package com.ruoyi.framework.config;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.ruoyi.common.utils.StringUtils;

import io.swagger.models.auth.In;
import springfox.documentation.RequestHandler;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.Contact;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.service.SecurityScheme;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.ApiSelectorBuilder;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;

/**
 * Swagger2的接口配置
 * 
 * @author ruoyi
 */
@Configuration
@EnableSwagger2WebMvc
public class SwaggerConfig
{
    /** 系统基础配置 */
    @Autowired
    private RuoYiConfig ruoyiConfig;

    /** 是否开启swagger */
    @Value("${swagger.enabled}")
    private boolean enabled;

    /** 设置请求的统一前缀 */
    @Value("${swagger.pathMapping}")
    private String pathMapping;

    /**
     * 分组名称
     */
    public static final String GROUP_RUOYI = "RuoYi";
    
    /**
     * 创建API
     */
    @Bean
    public Docket createRestApi()
    {
        return createApi(apiInfo(), GROUP_RUOYI, Arrays.asList("com.ruoyi.project.monitor.controller",
                "com.ruoyi.project.system.controller", "com.ruoyi.project.tool.gen.controller"));
    }

    @Bean
    public Docket createDocsRestApi()
    {
        return createApi(docsApiInfo(), "文档便利店",
                Arrays.asList("com.ruoyi.cms.folder.controller", "com.ruoyi.cms.docs.controller"));
    }

    /**
     * 安全模式，这里指定token通过Authorization头请求头传递
     */
    private List<SecurityScheme> securitySchemes()
    {
        List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>();
        apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue()));
        return apiKeyList;
    }

    /**
     * 安全上下文
     */
    private List<SecurityContext> securityContexts()
    {
        List<SecurityContext> securityContexts = new ArrayList<>();
        securityContexts.add(
                SecurityContext.builder()
                        .securityReferences(defaultAuth())
//                        .operationSelector(o -> o.requestMappingPattern().matches("/.*"))
                        .build());
        return securityContexts;
    }

    /**
     * 默认的安全上引用
     */
    private List<SecurityReference> defaultAuth()
    {
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        List<SecurityReference> securityReferences = new ArrayList<>();
        securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
        return securityReferences;
    }

    /**
     * 添加摘要信息
     */
    private ApiInfo apiInfo()
    {
        // 用ApiInfoBuilder进行定制
        return new ApiInfoBuilder()
                // 设置标题
                .title("标题：若依管理系统_接口文档")
                // 描述
                .description("描述：用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...")
                // 作者信息
                .contact(new Contact(ruoyiConfig.getName(), null, null))
                // 版本
                .version("版本号:" + ruoyiConfig.getVersion())
                .build();
    }
    
    /**
     * 添加摘要信息
     */
    private ApiInfo docsApiInfo()
    {
        // 用ApiInfoBuilder进行定制
        return new ApiInfoBuilder()
                // 设置标题
                .title("文档便利店_接口文档")
                // 描述
                .description("描述：")
                // 作者信息
                .contact(new Contact("ning", null, null))
                // 版本
                .version("版本号:" + ruoyiConfig.getVersion())
                .build();
    }
    
    /**
     * 创建API
     * @param apiInfo    api信息
     * @param groupName  分组名称
     * @param basePackage 扫描指定包中的swagger注解
     * @return
     */
    private Docket createApi(ApiInfo apiInfo , String groupName , List<String> basePackage)
	{
		ApiSelectorBuilder builder = new Docket(DocumentationType.SWAGGER_2)
				// 是否启用Swagger
				.enable(enabled)
				// 用来创建该API的基本信息，展示在文档的页面中（自定义展示的信息）
				.apiInfo(apiInfo).groupName(groupName)
				// 设置哪些接口暴露给Swagger展示
				.select()
				// 扫描所有 .apis(RequestHandlerSelectors.any())
				.paths(PathSelectors.any());
		if (StringUtils.isNotEmpty(basePackage))
		{
			// 扫描指定包中的swagger注解
			// .apis(RequestHandlerSelectors.basePackage("com.ruoyi.project.tool.swagger"))
			builder.apis(basePackage(basePackage));
		}
//		else
//		{
//			// 扫描所有有注解的api，用这种方式更灵活
//			builder.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class));
//		}
		Docket docket = builder.build()
				/* 设置安全模式，swagger可以设置访问token */
				.securitySchemes(securitySchemes()).securityContexts(securityContexts()).pathMapping(pathMapping);
		return docket;
	}
    
    /**
     * https://blog.csdn.net/weixin_38676276/article/details/113001227
     * https://blog.csdn.net/white_ShiKu/article/details/97782267
     * 重写basePackage方法，使能够实现多包访问
     *
     * @param basePackage
     * @return java.util.function.Predicate<springfox.documentation.RequestHandler>
     */
    public static Predicate<RequestHandler> basePackage(final List<String> basePackage) {
        return input -> declaringClass(input).map(handlerPackage(basePackage)).orElse(true);
    }

    private static java.util.function.Function<Class<?>, Boolean> handlerPackage(final List<String> basePackage) {
        return input -> {
            // 循环判断匹配
            for (String strPackage : basePackage) {
                boolean isMatch = input.getPackage().getName().startsWith(strPackage);
                if (isMatch) {
                    return true;
                }
            }
            return false;
        };
    }

    @SuppressWarnings("deprecation")
    private static Optional<? extends Class<?>> declaringClass(RequestHandler input) {
        return Optional.ofNullable(input.declaringClass());
    }

}
